#pragma once

#include "Vec4.h"

class Mat4
{
	Vec4  _m[4];

public:
	Mat4( const float d = float(1.0) )
	{
		_m[0].x = d;
		_m[1].y = d;
		_m[2].z = d;
		_m[3].w = d;
	}

	Mat4( const Vec4& a, const Vec4& b, const Vec4& c, const Vec4& d )
	{
		_m[0] = a; 
		_m[1] = b;  
		_m[2] = c;
		_m[3] = d; 
	}

	Mat4(float m00, float m10, float m20, float m30,
		 float m01, float m11, float m21, float m31,
		 float m02, float m12, float m22, float m32,
		 float m03, float m13, float m23, float m33 )
	{
		_m[0] = Vec4( m00, m01, m02, m03 );
		_m[1] = Vec4( m10, m11, m12, m13 );
		_m[2] = Vec4( m20, m21, m22, m23 );
		_m[3] = Vec4( m30, m31, m32, m33 );
	}

	Mat4( const Mat4& m )
	{
		if ( *this != m ) {
			_m[0] = m._m[0];
			_m[1] = m._m[1];
			_m[2] = m._m[2];
			_m[3] = m._m[3];
		} 
	}

	Mat4 transpose() const
	{
		return Mat4(
			Vec4(_m[0][0], _m[1][0], _m[2][0], _m[3][0]),
			Vec4(_m[0][1], _m[1][1], _m[2][1], _m[3][1]),
			Vec4(_m[0][2], _m[1][2], _m[2][2], _m[3][2]),
			Vec4(_m[0][3], _m[1][3], _m[2][3], _m[3][3])
		);
	}

	Vec4& operator [] ( int i ) { return _m[i]; }
	const Vec4& operator [] ( int i ) const { return _m[i]; }

	Mat4 operator + ( const Mat4& m ) const
	{
		return Mat4( _m[0]+m[0], _m[1]+m[1], _m[2]+m[2], _m[3]+m[3] );
	}

	Mat4 operator - ( const Mat4& m ) const
	{
		return Mat4( _m[0]-m[0], _m[1]-m[1], _m[2]-m[2], _m[3]-m[3] );
	}

	Mat4 operator * ( const float s ) const 
	{
		return Mat4( s*_m[0], s*_m[1], s*_m[2], s*_m[3] );
	}

	Mat4 operator / ( const float s ) const
	{
		float r = float(1.0) / s;
		return *this * r;
	}

	friend Mat4 operator * ( const float s, const Mat4& m )
	{
		return m * s;
	}

	Mat4 operator * ( const Mat4& m ) const
	{
		Mat4  a( 0.0 );

		for (int i = 0; i < 4; ++i) {
			for (int j = 0; j < 4; ++j) {
				for (int k = 0; k < 4; ++k) {
					a[i][j] += _m[i][k] * m[k][j];
				}
			}
		}

		return a;
	}

	Mat4& operator += ( const Mat4& m )
	{
		_m[0] += m[0];
		_m[1] += m[1];
		_m[2] += m[2];
		_m[3] += m[3];
		return *this;
	}

	Mat4& operator -= ( const Mat4& m )
	{
		_m[0] -= m[0]; 
		_m[1] -= m[1];
		_m[2] -= m[2];
		_m[3] -= m[3];
		return *this;
	}

	Mat4& operator *= ( const float s )
	{
		_m[0] *= s;  
		_m[1] *= s; 
		_m[2] *= s; 
		_m[3] *= s;
		return *this;
	}

	Mat4& operator *= ( const Mat4& m )
	{
		Mat4  a( 0.0 );

		for ( int i = 0; i < 4; ++i ) {
			for ( int j = 0; j < 4; ++j ) {
				for ( int k = 0; k < 4; ++k ) {
					a[i][j] += _m[i][k] * m[k][j];
				}
			}
		}

		return *this = a;
	}

	Mat4& operator /= ( const float s )
	{
		float r = float(1.0) / s;
		return *this *= r;
	}

	Vec4 operator * ( const Vec4& v ) const
	{
		return Vec4(
			_m[0][0]*v.x + _m[0][1]*v.y + _m[0][2]*v.z + _m[0][3]*v.w,
			_m[1][0]*v.x + _m[1][1]*v.y + _m[1][2]*v.z + _m[1][3]*v.w,
			_m[2][0]*v.x + _m[2][1]*v.y + _m[2][2]*v.z + _m[2][3]*v.w,
			_m[3][0]*v.x + _m[3][1]*v.y + _m[3][2]*v.z + _m[3][3]*v.w
		);
	}

	friend std::ostream& operator << ( std::ostream& os, const Mat4& m )
	{
		return os << std::endl 
			<< m[0] << std::endl
			<< m[1] << std::endl
			<< m[2] << std::endl
			<< m[3] << std::endl;
	}

	friend std::istream& operator >> ( std::istream& is, Mat4& m )
	{
		return is >> m._m[0] >> m._m[1] >> m._m[2] >> m._m[3];
	}

	operator const float* () const
	{
		return static_cast<const float*>( &_m[0].x );
	}

	operator float* ()
	{
		return static_cast<float*>( &_m[0].x );
	}
};
